package com.purebang.spaceinvaders;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;

import javax.swing.ImageIcon;

import com.purebang.spaceinvaders.DestroyedAsset.DestroyedType;
import com.purebang.spaceinvaders.Enemy.EnemyMoveDirection;
import com.purebang.spaceinvaders.Enemy.EnemyType;

public class GameManager extends Applet implements Runnable {

	//KeyListener that records player input
	private PlayerInput input = null;
	//Path to this directory
	//Images for game
	private Image iLaser, iCannon, iBottomEnemy, iMiddleEnemy, iTopEnemy, iWeakProjectile,
			iMiddleProjectile, iStrongProjectile, iMysteryShip, iCannonDestroyed, iEnemyDestroyed,
			iMysteryShipDestroyed, iProjectileHit, iLaserHit;
	//Used to draw the game screen
	private Image image = null;
	private Graphics second = null;
	
	//Game objects
	private LaserCannon cannon = null;
	private Laser laser = null;
	private List<Enemy> enemyFleet;
	private List<Projectile> enemyProjectiles;
	private List<Bunker> bunkers;
	private List<DestroyedAsset> destroyedAssets;
	private Random rand;
	private Player player;
	private MysteryShip mysteryShip;
	
	//Game variables
	private int highScore;
	private int enemyFleetsDestroyed = 0;
	private int levelsDescended = 0;
	private int enemySpeedAdder = 0;		//Increases enemy speed
	private int gameOverTimer = 300; 		//Number of frames to display "Game Over"
	private int playerHitTimer = 60; 		//Number of frames to freeze when player is hit
	private int fleetDestroyedTimer = 60;	//Number of frames to wait when fleet is destroyed
	private long mysteryShipTimer = 0;
	private long mysteryShipSpawn = 1200;	//Base Number of frames b4 MysteryShip spawns
	private long mysteryShipSpawnTime = 0;
	private int shipSpawnTimeVariance=480;	//Number of variance frames for MysteryShip spawn
	private int shipScoreTimer = 60;		//Number of Frames to display destroyed mysteryship score
	private int shipScoreAwarded = 0;		//Variables for drawing mysteryship score
	private int shipScoreXpos = 0;			//Variables for drawing mysteryship score
	private int shipScoreMultiplier = 1;	//Multiplier for consective mysteryship kills
	private int freezeFrame = 30;			//Number of frames to wait before updating enemies
	private boolean laserAlreadyFired=false;//True when laser is on screen
	private boolean playerIsHit = false;
	private boolean changeEnemyDirection = false;
	private boolean fleetDestroyed = false;
	private boolean enemiesInvaded = false;	//GameOver condition
	private boolean mysteryShipExists = false;
	private boolean mysteryShipDestroyed = false;
	private boolean gameOver = false;
	
	private boolean debug = false;			//Displays additional information on screen when true
	
	//Starting player info
	private final int STARTING_SCORE = 0;
	private final int STARTING_LIVES = 3;
	//Level Dimensions
	public static final int LEVEL_HEIGHT = 800;
	public static final int LEVEL_WIDTH = 680;
	//X spacing between enemies in fleet
	public static final int ENEMY_X_PADDING = 10;
	//Y spacing between enemies in fleet
	public static final int ENEMY_Y_PADDING = 10;
	//If enemy passes this X, the enemy fleet will change directions
	public static final int ENEMY_MAX_XLEFT = 60;
	//If enemy passes this X, the enemy fleet will change directions
	public static final int ENEMY_MAX_XRIGHT = LEVEL_WIDTH - 60;	
	//Laser is destroyed if it misses all objectives and reaches this Y bound
	private final int MAX_LASER_HEIGHT = 50;
	//Projectile destroyed if it misses all objectives and reaches this Y bound
	private final int MAX_ENEMY_PROJECTILE_HEIGHT = 750;
	//Game is over if an enemy reaches this Y
	private final int GAME_OVER_LINE = 660;
	
	//Cannon Spawn
	private final int SPAWN_CANNON_X = LEVEL_WIDTH /2 - LaserCannon.WIDTH / 2;
	private final int SPAWN_CANNON_Y = 690;
	
	//Mysteryship spawn
	private final int SPAWN_MYSTERYSHIP_Y = MAX_LASER_HEIGHT + 5;
	
	//Number of columns/rows in enemy fleet
	private final int ENEMY_COLUMNS = 11;
	private final int ENEMY_ROWS = 5;
	//Minimum number of descents enemy needs to reach game over line
	private final int MIN_ENEMY_DESCENTS = 11;
	//Bottom two rows of enemy fleet
	private final int NUM_BOTTOM_ENEMIES = 22;
	//Middle two rows of enemy fleet
	private final int NUM_MIDDLE_ENEMIES = 22;
	//Top row of enemy fleet
	private final int NUM_TOP_ENEMIES = 11;
	//Score awarded for destroying a bottom enemy
	private final int BOTTOM_ENEMY_POINTS = 10;
	//Score awarded for destroying a middle enemy
	private final int MIDDLE_ENEMY_POINTS = 20;
	//Score awarded for destroying a top enemy
	private final int TOP_ENEMY_POINTS = 40;
	
	//Bunker Spawn
	private final int NUM_BUNKERS = 4;
	private final int SPAWN_BUNKER_Y = SPAWN_CANNON_Y - 120;
	
	//text for current game score
	private final int SCORE_TEXT_XPOS = 10;
	private final int SCORE_TEXT_YPOS = 40;
	private final int SCORE_FONT_SIZE = 24;
	private final String SCORE_STRING = "Score: ";
	
	//text for current lives
	private final int LIVES_TEXT_XPOS = LEVEL_WIDTH - 140;
	private final int LIVES_TEXT_YPOS = 40;
	private final int LIVES_FONT_SIZE = 24;
	private final String LIVES_STRING = "Lives: ";
	
	//text for high score
	private final int HSCORE_TEXT_YPOS = 40;
	private final int HSCORE_FONT_SIZE = 24;
	private final String HSCORE_STRING = "High Score: ";
	
	//game over text
	private final int GAMEOVER_TEXT_XPOS = LEVEL_WIDTH / 2;
	private final int GAMEOVER_TEXT_YPOS = LEVEL_HEIGHT /2;
	private final int GAMEOVER_FONT_SIZE = 70;
	private final String GAMEOVER_STRING = "GAME OVER";
	
	//score displayed when mystery ship killed
	private final int SHIPSCORE_FONT_SIZE = 26;
	

	@Override
	public void init() {
		//Add keyboard listener to Java Applet
		rand = new Random();
		input = new PlayerInput();
		addKeyListener(input);
		
		//Set the level size, background, and make it focusable.
		setSize(LEVEL_WIDTH, LEVEL_HEIGHT);
		setBackground(Color.BLACK);
		setFocusable(true);
		
		//Frame frame = (Frame) this.getParent().getParent();
		//frame.setTitle("SpaceInvaders");
		
		//Initialize images
		iCannon = new ImageIcon(getClass().getResource("res/cannon.png")).getImage();
		iLaser = new ImageIcon(getClass().getResource("res/laser.png")).getImage();
		iBottomEnemy = new ImageIcon(getClass().getResource("res/bottommonster.png")).getImage();
		iMiddleEnemy = new ImageIcon(getClass().getResource("res/middlemonster.png")).getImage();
		iTopEnemy = new ImageIcon(getClass().getResource("res/topmonster.png")).getImage();
		iWeakProjectile = new ImageIcon(getClass().getResource("res/weakprojectile.png")).getImage();
		iMiddleProjectile = new ImageIcon(getClass().getResource("res/mediumprojectile.png")).getImage();
		iStrongProjectile = new ImageIcon(getClass().getResource("res/strongprojectile.png")).getImage();
		iMysteryShip = new ImageIcon(getClass().getResource("res/mysteryship.png")).getImage();
		iCannonDestroyed = new ImageIcon(getClass().getResource("res/cannondestroyed.png")).getImage();
		iEnemyDestroyed = new ImageIcon(getClass().getResource("res/enemydestroyed.png")).getImage();
		iMysteryShipDestroyed = new ImageIcon(getClass().getResource("res/mysteryshipdestroyed.png")).getImage();
	}
	
	@Override
	public void update(Graphics g) {
		
		//double buffering technique
		if (image == null) {
			image = createImage(this.getWidth(), this.getHeight());
			second = image.getGraphics();
		}

		second.setColor(getBackground());
		second.fillRect(0, 0, getWidth(), getHeight());
		second.setColor(getForeground());
		paint(second);

		g.drawImage(image, 0, 0, this);
	}
	
	@Override
	public void paint(Graphics g) {
		
		//Draw Score text
		g.setColor(Color.WHITE);
		Font scoreFont = new Font(SCORE_STRING, Font.PLAIN, SCORE_FONT_SIZE);		
		g.setFont(scoreFont);
		FontMetrics scoreMetrics = g.getFontMetrics(scoreFont);
		g.drawString(SCORE_STRING, SCORE_TEXT_XPOS, SCORE_TEXT_YPOS);
		g.drawString(Integer.toString(player.score), SCORE_TEXT_XPOS + scoreMetrics.stringWidth(SCORE_STRING),
				SCORE_TEXT_YPOS);
		
		//Draw Lives text
		Font livesFont = new Font(LIVES_STRING, Font.PLAIN, LIVES_FONT_SIZE);		
		g.setFont(livesFont);
		FontMetrics livesMetrics = g.getFontMetrics(livesFont);
		g.drawString(LIVES_STRING, LIVES_TEXT_XPOS, LIVES_TEXT_YPOS);
		g.drawString(Integer.toString(player.lives), LIVES_TEXT_XPOS + livesMetrics.stringWidth(LIVES_STRING),
				LIVES_TEXT_YPOS);
		
		//Draw High Score text
		g.setColor(Color.WHITE);
		Font hscoreFont = new Font(HSCORE_STRING, Font.PLAIN, HSCORE_FONT_SIZE);		
		g.setFont(hscoreFont);
		FontMetrics hscoreMetrics = g.getFontMetrics(hscoreFont);
		int hScoreTextXpos = LEVEL_WIDTH / 2 - (hscoreMetrics.stringWidth(HSCORE_STRING) / 2 +
				hscoreMetrics.stringWidth(Integer.toString(highScore)));
		g.drawString(HSCORE_STRING, hScoreTextXpos, HSCORE_TEXT_YPOS);
		g.drawString(Integer.toString(highScore), hScoreTextXpos + hscoreMetrics.stringWidth(HSCORE_STRING),
				HSCORE_TEXT_YPOS);
		
		
		//Draw the top and bottom lines
		g.setColor(Color.GREEN);
		g.drawLine(0, MAX_LASER_HEIGHT, LEVEL_WIDTH, MAX_LASER_HEIGHT);
		g.drawLine(0, MAX_ENEMY_PROJECTILE_HEIGHT, LEVEL_WIDTH, MAX_ENEMY_PROJECTILE_HEIGHT);
		
		
		//debug
		if(debug) {
			g.drawLine(ENEMY_MAX_XLEFT, 0, ENEMY_MAX_XLEFT, LEVEL_HEIGHT);
			g.drawLine(ENEMY_MAX_XRIGHT, 0, ENEMY_MAX_XRIGHT, LEVEL_HEIGHT);
			g.drawLine(0,GAME_OVER_LINE,LEVEL_WIDTH,GAME_OVER_LINE);
		}
		
		
		//Draw the Laser Cannon
		if(!gameOver) {
			if(debug) {
				g.setColor(Color.WHITE);
				g.drawRect(cannon.getHitbox().x, cannon.getHitbox().y, 
						cannon.getHitbox().width, cannon.getHitbox().height);
			}
			g.drawImage(iCannon, cannon.getHitbox().x, cannon.getHitbox().y, cannon.getHitbox().width,
					cannon.getHitbox().height, this);

			//Draw the laser
			if(laserAlreadyFired) {
				if(debug) {
					g.drawRect(laser.getHitbox().x, laser.getHitbox().y,
							laser.getHitbox().width, laser.getHitbox().height);
				}
				g.drawImage(iLaser, laser.getHitbox().x, laser.getHitbox().y,
						laser.getHitbox().width, laser.getHitbox().height, this);

			}

			//Draw the MysteryShip
			if(mysteryShipExists) {
				if(debug) {
					g.drawRect(mysteryShip.getHitbox().x, mysteryShip.getHitbox().y, 
							mysteryShip.getHitbox().width, mysteryShip.getHitbox().height);
				}
				g.drawImage(iMysteryShip, mysteryShip.getHitbox().x, mysteryShip.getHitbox().y, 
						mysteryShip.getHitbox().width, mysteryShip.getHitbox().height, this);
			}

			//Draw the enemies
			for (Iterator<Enemy> i = enemyFleet.iterator(); i.hasNext();) {
				Enemy enemy = i.next();
				if(debug) {
					g.drawRect(enemy.getHitbox().x, enemy.getHitbox().y, enemy.getHitbox().width,
							enemy.getHitbox().height);
				}
				switch(enemy.getType()) {
				case top:
					g.drawImage(iTopEnemy,enemy.getHitbox().x, enemy.getHitbox().y, enemy.getHitbox().width,
							enemy.getHitbox().height, this);
					break;
				case middle:
					g.drawImage(iMiddleEnemy,enemy.getHitbox().x, enemy.getHitbox().y, enemy.getHitbox().width,
							enemy.getHitbox().height, this);
					break;
				case bottom:
					g.drawImage(iBottomEnemy,enemy.getHitbox().x, enemy.getHitbox().y, enemy.getHitbox().width,
							enemy.getHitbox().height, this);
					break;
				}
			}

			//Draw the projectiles
			for (Iterator<Projectile> i = enemyProjectiles.iterator(); i.hasNext();) {
				Projectile projectile = i.next();
				if(debug) {
					g.drawRect(projectile.getHitbox().x, projectile.getHitbox().y, 
							projectile.getHitbox().width, projectile.getHitbox().height);
				}
				switch(projectile.getType()) {
				case strong:
					g.drawImage(iStrongProjectile, projectile.getHitbox().x, projectile.getHitbox().y, 
							projectile.getHitbox().width, projectile.getHitbox().height, this);
					break;
				case medium:
					g.drawImage(iMiddleProjectile, projectile.getHitbox().x, projectile.getHitbox().y, 
							projectile.getHitbox().width, projectile.getHitbox().height, this);
					break;
				case weak:
					g.drawImage(iWeakProjectile, projectile.getHitbox().x, projectile.getHitbox().y, 
							projectile.getHitbox().width, projectile.getHitbox().height, this);
					break;
				}
			}
		}//end if !gameOver
			
		//Draw the bunkers
		for (Iterator<Bunker> i = bunkers.iterator(); i.hasNext();) {
			Bunker bunker = i.next();
			for(Iterator<BunkerSection> j = bunker.sections.iterator(); j.hasNext();) {
				BunkerSection section = j.next();	
				g.drawRect(section.getSectionHitBox().x, section.getSectionHitBox().y,
						section.getSectionHitBox().width, section.getSectionHitBox().height);
			}
		}

		//Draw destroyed images
		for (Iterator<DestroyedAsset> i = destroyedAssets.iterator(); i.hasNext();) {
			DestroyedAsset asset = i.next();
			switch(asset.getType()) {
			case cannon:
				g.drawImage(iCannonDestroyed, asset.getHitbox().x, asset.getHitbox().y,
						asset.getHitbox().width, asset.getHitbox().height, this);
				break;
			case enemy:
				g.drawImage(iEnemyDestroyed, asset.getHitbox().x, asset.getHitbox().y,
						asset.getHitbox().width, asset.getHitbox().height, this);
				break;
			case laser:
				break;
			case projectile:
				break;
			case mystery_ship:
				mysteryShipDestroyed = true;
				shipScoreXpos = asset.getHitbox().x;
				g.drawImage(iMysteryShipDestroyed, asset.getHitbox().x, asset.getHitbox().y,
						asset.getHitbox().width, asset.getHitbox().height, this);
				
				break;
			case bunker_section:
				break;
			}
		}
		
		//Draw mysteryship score award
		if(mysteryShipDestroyed) {
			shipScoreTimer--;
			String scoreString = Integer.toString(shipScoreXpos);
			g.setColor(Color.WHITE);
			Font shipScoreFont = new Font(scoreString, Font.PLAIN, SHIPSCORE_FONT_SIZE);		
			g.setFont(shipScoreFont);
			FontMetrics shipScoreMetrics = g.getFontMetrics(shipScoreFont);
			if(shipScoreXpos <= 0) {
				shipScoreXpos = 10;
			}else if (shipScoreXpos > (LEVEL_WIDTH - shipScoreMetrics.stringWidth(scoreString))) {
				shipScoreXpos = LEVEL_WIDTH - 10 - shipScoreMetrics.stringWidth(scoreString);
			}
			g.drawString(Integer.toString(shipScoreAwarded), shipScoreXpos,
					SPAWN_MYSTERYSHIP_Y + MysteryShip.SHIP_HEIGHT);
			if(shipScoreTimer == 0) {
				shipScoreTimer = 60;
				mysteryShipDestroyed = false;
			}
		}
		
		//Draw game over text
		if(gameOver) {
			g.setColor(Color.WHITE);
			Font overFont = new Font(GAMEOVER_STRING, Font.PLAIN, GAMEOVER_FONT_SIZE);		
			g.setFont(overFont);
			FontMetrics overMetrics = g.getFontMetrics(overFont);
			g.drawString(GAMEOVER_STRING, LEVEL_WIDTH /2 - overMetrics.stringWidth(GAMEOVER_STRING)/2,
					GAMEOVER_TEXT_YPOS);
		}
		
		
	}
	
	@Override
	public void run() {
		while (true) {
			
			//Game Over work
			if (player.lives == 0 || enemiesInvaded) {
				gameOver = true;
				gameOverTimer --;
				//Reinitialize game
				if (gameOverTimer == 0) {
					if(player.score > highScore) {
						saveScore(player.score);
						loadScore();
					}
					resetGameVariables();
					createGameObjects();
					spawnEnemyFleet();
					spawnBunkers();
					spawnMysteryShip();
				}
				repaint();
			}
			
			//Reset projectiles if player is hit
			if(playerIsHit) {
				playerHitTimer--;
				if (playerHitTimer == 0) {
					
					//Reset projectiles that enemies fired
					for(Iterator<Projectile> i = enemyProjectiles.iterator(); i.hasNext();) {
						Projectile projectile = i.next();
						for(Iterator<Enemy> j = enemyFleet.iterator(); j.hasNext(); ) {
							Enemy enemy = j.next();
							if (projectile.getID() == enemy.getID()) {
								enemy.projectileHit();
							}
						}
					}
					//Remove a life for being hit
					player.lives--;

					//Clear enemy projectiles
					enemyProjectiles.clear();
					
					//Clear laser and player no longer hit
					laserAlreadyFired = false;
					playerIsHit = false;
					playerHitTimer = 60;
					
					//Clear dead objects
					destroyedAssets.clear();
					
					//Create a new cannon for player
					cannon = new LaserCannon(SPAWN_CANNON_X, SPAWN_CANNON_Y, input);
				}
				repaint();
			}
			
			//Reset enemies, projectiles, and mysteryship if fleet is destroyed
			if(fleetDestroyed) {
				fleetDestroyedTimer --;
				if(fleetDestroyedTimer == 0) {
					enemyFleetsDestroyed ++;
					if (enemyFleetsDestroyed == 11) {
						enemyFleetsDestroyed = 0;
						enemySpeedAdder ++;
					}
						mysteryShipTimer = 0;
						mysteryShip = null;
						mysteryShipExists = false;
					
					//Update player lives
					player.lives++;
					playerIsHit = false;
					//reset cannon
					cannon = new LaserCannon(SPAWN_CANNON_X, SPAWN_CANNON_Y, input);
					laserAlreadyFired = false;
					//spawn new mysteryship
					mysteryShipDestroyed = false;
					shipScoreMultiplier = 1;
					spawnMysteryShip();
					shipScoreTimer = 60;
					//spawn new enemies and give them faster speed
					fleetDestroyed = false;
					fleetDestroyedTimer = 60;
					spawnEnemyFleet();
					for(Iterator<Enemy> i = enemyFleet.iterator(); i.hasNext(); ) {
						Enemy enemy = i.next();
						int speed = enemy.getxSpeed() + enemySpeedAdder;
						enemy.setxSpeed(speed);
					}
					
					//Clear enemy projectiles
					enemyProjectiles.clear();
					//reset freeze frames
					freezeFrame = 30;
					//clear destroyed objects
					destroyedAssets.clear();
				}
			}
			
			if(!gameOver && !playerIsHit && !fleetDestroyed) {
				cannon.update();

				//Check to see if laser exists if player wants to fire a laser
				if(!laserAlreadyFired && cannon.isLaserFired()) {
					laserAlreadyFired = true;
					laser = new Laser(cannon.getHitbox());
					player.shotsFired ++;
				}else if(laserAlreadyFired) {
					laser.update();
				}

				//Update enemy projectiles
				for (Iterator<Projectile> i = enemyProjectiles.iterator(); i.hasNext();) {
					Projectile projectile = i.next();
					projectile.update();
				}
				
				//Check if laser has destroyed enemy or mysteryship
				if(laserAlreadyFired) {
					for (Iterator<Enemy> i = enemyFleet.iterator(); i.hasNext();) {
						Enemy enemy = i.next();
						//Remove enemy if laser hit them and add to player score
						if (enemy.getHitbox().intersects(laser.getHitbox())) {
							player.score = player.score + enemy.getScore();
							DestroyedAsset asset = new DestroyedAsset(enemy.getHitbox(),
									DestroyedType.enemy);
							destroyedAssets.add(asset);
							i.remove();
							laserAlreadyFired = false;
							break;
						}
					}

					//check if mysteryship is destroyed by laser
					if(mysteryShipExists) {
						if(mysteryShip.getHitbox().intersects(laser.getHitbox())) {
							DestroyedAsset asset = new DestroyedAsset(mysteryShip.getHitbox(),
									DestroyedType.mystery_ship);
							destroyedAssets.add(asset);
							player.score = player.score + (shipScoreMultiplier * mysteryShip.getScore());
							shipScoreAwarded = mysteryShip.getScore() * shipScoreMultiplier;
							shipScoreMultiplier ++;
							mysteryShipExists = false;
							laserAlreadyFired = false;
						}
					}
				}

				//update mystery ship
				if (mysteryShipExists) {
					if(mysteryShip.isEscaped()) {
						mysteryShipExists = false;
						shipScoreMultiplier = 1;
					}else
						mysteryShip.update();
				}
				//Move enemy every (60 / (freezeFrame)) frames per second
				//Remove all destroyed images
				if(freezeFrame == 0) {
					destroyedAssets.clear();
					if (enemyFleet.size() > 0) {
						freezeFrame = 30 - 15 * (ENEMY_COLUMNS * ENEMY_ROWS - enemyFleet.size()) /
								(ENEMY_COLUMNS * ENEMY_ROWS) -
								((15 * levelsDescended) / MIN_ENEMY_DESCENTS);
					}
					
					for (Iterator<Enemy> i = enemyFleet.iterator(); i.hasNext();) {
						Enemy enemy = i.next();
						enemy.update();
						//Notify GameManager if an enemy has reached an X bound
						if (enemy.isChangeDirection()) {
							changeEnemyDirection = true;
						}
						//Randomly spawn a missile
						int enemiesRemaining = ((ENEMY_COLUMNS * ENEMY_ROWS) - enemyFleet.size());
						int chance = rand.nextInt(150 - enemiesRemaining);
						if(chance == 1 && enemy.getProjectilesFired() < 3) {
							enemy.fire();
							Projectile projectile = new Projectile(enemy.getHitbox(), enemy.getID());
							enemyProjectiles.add(projectile);
						}
						
						//Enemies reached end line, game is over
						if (enemy.getHitbox().y >= GAME_OVER_LINE) {
							enemiesInvaded = true;
						}
					}
				}else
					freezeFrame--;
				
				//flag if fleet was completely destroyed
				if (enemyFleet.size() == 0) {
					fleetDestroyed = true;
				}

				//Check if projectiles hit player and remove all projectiles if it did.
				for (Iterator<Projectile> i = enemyProjectiles.iterator(); i.hasNext();) {
					Projectile projectile = i.next();
					if(cannon.getHitbox().intersects(projectile.getHitbox())) {
						for(Iterator<Enemy> j = enemyFleet.iterator(); j.hasNext();) {
							Enemy enemy = j.next();
							if (enemy.getProjectilesFired() > 0)
								enemy.resetProjectilesFired();
						}
						playerIsHit = true;
						DestroyedAsset asset = new DestroyedAsset(cannon.getHitbox(), DestroyedType.cannon);
						destroyedAssets.add(asset);
						break;
					}
				}

				//Check if laser or projectile hit bunker
				for (Iterator<Bunker> i = bunkers.iterator(); i.hasNext(); ) {
					Bunker bunker = i.next();
					for (Iterator<BunkerSection> j = bunker.sections.iterator(); j.hasNext(); ) {
						BunkerSection section = j.next();
						if (laserAlreadyFired) {
							if(section.isHit(laser)) {
								DestroyedAsset asset = new DestroyedAsset(laser.getHitbox(),
										DestroyedType.laser);
								destroyedAssets.add(asset);
								laserAlreadyFired = false;
							}
						}
						//check if projectile hit bunker
						for(Iterator<Projectile> k = enemyProjectiles.iterator(); k.hasNext();) {
							Projectile projectile = k.next();
							//register hit with enemy and remove projectile
							if(section.isHit(projectile)) {
								for(Iterator<Enemy> l = enemyFleet.iterator(); l.hasNext();) {
									Enemy enemy = l.next();
									if(projectile.getID() == enemy.getID()) {
										enemy.projectileHit();
										break;
									}
								}
								DestroyedAsset asset = new DestroyedAsset(projectile.getHitbox(),
										DestroyedType.projectile);
								destroyedAssets.add(asset);
								k.remove();
							}
						}
						//Remove any destroyed sections
						if(section.isDestroyed())
							j.remove();
					}
				}

				//Prepare enemy fleet for descent
				if (changeEnemyDirection) {
					for (Iterator<Enemy> i = enemyFleet.iterator(); i.hasNext();) {
						Enemy enemy = i.next();
						enemy.setChangeDirection(true);
					}
					changeEnemyDirection = false;
					if (levelsDescended <= MIN_ENEMY_DESCENTS)
						levelsDescended ++;
				}

				//If laser misses and reaches top of screen, allow player to fire next laser
				if(laserAlreadyFired) {
					if(laser.getHitbox().y <= MAX_LASER_HEIGHT) {
						laserAlreadyFired = false;
						DestroyedAsset asset = new DestroyedAsset(laser.getHitbox(),
								DestroyedType.laser);
						destroyedAssets.add(asset);
					}
				}

				//If projectile misses and reaches bottom of screen, delete projectile
				for (Iterator<Projectile> i = enemyProjectiles.iterator(); i.hasNext();) {
					Projectile projectile = i.next();
					if ((projectile.getHitbox().y + projectile.getHitbox().height) >= MAX_ENEMY_PROJECTILE_HEIGHT) {
						for(Iterator<Enemy> j = enemyFleet.iterator(); j.hasNext(); ) {
							Enemy enemy = j.next();
							if (projectile.getID() == enemy.getID()) {
								enemy.projectileHit();
							}
						}
						DestroyedAsset asset = new DestroyedAsset(projectile.getHitbox(),
								DestroyedType.projectile);
						destroyedAssets.add(asset);
						i.remove();
					}
				}

				if(!mysteryShipExists)
					mysteryShipTimer ++;
				//Spawn a mysteryShip
				if(mysteryShipTimer == mysteryShipSpawnTime) {
					mysteryShipExists = true;
					int spawnShip = rand.nextInt(2);
					if (spawnShip == 0) {
						mysteryShip = new MysteryShip(0 - MysteryShip.SHIP_WIDTH, SPAWN_MYSTERYSHIP_Y,
								100, MysteryShip.MoveDirection.right);
					}else {
						mysteryShip = new MysteryShip(LEVEL_WIDTH, SPAWN_MYSTERYSHIP_Y,
								100, MysteryShip.MoveDirection.left);
					}
					//calculate next mysteryShip spawn time
					spawnMysteryShip();
					mysteryShipTimer = 0;
				}

				repaint();
			} //if !game over || !fleetDestroyed || !playerIsHit
			try {
				Thread.sleep(17);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}
	}

	@Override
	public void start() {
		//Load score
		loadScore();
		//Initialize
		createGameObjects();
		spawnEnemyFleet();
		spawnBunkers();
		spawnMysteryShip();
		
		Thread thread = new Thread(this);
		thread.start();
	}
	
	private void createGameObjects() {
		cannon = new LaserCannon(SPAWN_CANNON_X, SPAWN_CANNON_Y, input);
		enemyFleet = new LinkedList<Enemy>();
		enemyProjectiles = new LinkedList<Projectile>();
		destroyedAssets = new LinkedList<DestroyedAsset>();
		bunkers = new LinkedList<Bunker>();
		player = new Player(STARTING_SCORE, STARTING_LIVES, 0);
	}
	
	private void spawnEnemyFleet() {
		//Calculations to spawn enemy fleet in middle of screen
				int startSpawnX = (LEVEL_WIDTH / 2) - ((Enemy.ENEMY_WIDTH + ENEMY_X_PADDING) * ENEMY_COLUMNS) / 2;
				int startSpawnY = GAME_OVER_LINE - (MIN_ENEMY_DESCENTS * 
						(Enemy.ENEMY_HEIGHT + ENEMY_Y_PADDING));
				startSpawnY = startSpawnY + enemyFleetsDestroyed * (Enemy.ENEMY_HEIGHT + ENEMY_Y_PADDING);
				int xSpacing = Enemy.ENEMY_WIDTH + ENEMY_X_PADDING;
				int ySpacing = Enemy.ENEMY_HEIGHT + ENEMY_Y_PADDING;
				int colCarriage = 0;
				
				//Make starting enemy fleet direction random
				int direction = rand.nextInt(2);
				EnemyMoveDirection startDirection;
				if(direction == 0) {
					startDirection = EnemyMoveDirection.left;
				}else {
					startDirection = EnemyMoveDirection.right;
				}
				
				//Initialize enemy fleet
				for (int i = 1; i <= (ENEMY_ROWS * ENEMY_COLUMNS); i++) {
					if (i <= NUM_BOTTOM_ENEMIES) {
						Enemy enemy = new Enemy(startSpawnX + (xSpacing * colCarriage),
								startSpawnY - (ySpacing * ((i-1)/ENEMY_COLUMNS)), BOTTOM_ENEMY_POINTS, i,
								startDirection,	EnemyType.bottom);
						enemyFleet.add(enemy);
					}else if (i <= (NUM_MIDDLE_ENEMIES + NUM_BOTTOM_ENEMIES)) {
						Enemy enemy = new Enemy(startSpawnX + (xSpacing * colCarriage),
								startSpawnY - (ySpacing * ((i-1)/ENEMY_COLUMNS)), MIDDLE_ENEMY_POINTS, i,
								startDirection, EnemyType.middle);
						enemyFleet.add(enemy);	
					}else if (i <= (NUM_TOP_ENEMIES + NUM_MIDDLE_ENEMIES + NUM_BOTTOM_ENEMIES)) {
						Enemy enemy = new Enemy(startSpawnX + (xSpacing * colCarriage),
								startSpawnY - (ySpacing * ((i-1)/ENEMY_COLUMNS)), TOP_ENEMY_POINTS, i,
								startDirection, EnemyType.top);
						enemyFleet.add(enemy);	
					}
					if (i % ENEMY_COLUMNS == 0) {
						colCarriage = 0;
					}else
						colCarriage ++;
				}
	}
	
	private void spawnBunkers() {
		//Create Bunkers
		int bunkerSpacing = ((ENEMY_MAX_XRIGHT - ENEMY_MAX_XLEFT) - (4 * Bunker.BUNKER_WIDTH))/4;
		bunkerSpacing = bunkerSpacing + Bunker.BUNKER_WIDTH;
		int spawnBunkX = ENEMY_MAX_XLEFT + bunkerSpacing / 4;
		
		for (int i = 0; i < NUM_BUNKERS; i++) {
			Bunker bunker = new Bunker(spawnBunkX + i * bunkerSpacing,
					SPAWN_BUNKER_Y);
			bunkers.add(bunker);
		}
	}
	
	
	private void spawnMysteryShip() {
		//Calculate first mysteryship spawntime
		mysteryShipSpawn = 1200;
		int variance = rand.nextInt(shipSpawnTimeVariance);
		int sign = rand.nextInt(2);
		if(sign == 0) {
			mysteryShipSpawnTime = mysteryShipSpawn - variance;
		}else
			mysteryShipSpawnTime = mysteryShipSpawn - variance;
	}
	
	private void resetGameVariables() {
		//Frame freeze for enemies/destroyables
		freezeFrame = 30;
		//Enemy variables
		enemyFleetsDestroyed = 0;
		fleetDestroyedTimer = 60;
		levelsDescended = 0;
		enemySpeedAdder = 0;
		changeEnemyDirection = false;
		fleetDestroyed = false;
		enemiesInvaded = false;
		//MysteryShip variables
		mysteryShipTimer = 0;
		mysteryShipSpawn = 1200;
		mysteryShipSpawnTime = 0;
		shipSpawnTimeVariance = 480;
		shipScoreTimer = 60;
		shipScoreAwarded = 0;
		shipScoreXpos = 0;
		mysteryShipExists = false;
		mysteryShipDestroyed = false;
		shipScoreMultiplier = 1;
		//PlayerRelated variables
		playerHitTimer = 60;
		laserAlreadyFired = false;
		playerIsHit = false;
		//Game Over variables
		gameOverTimer = 300;
		gameOver = false;
	}
	
	private void loadScore() {
		
		File file = new File("data/highscore.txt");
		try {
			Scanner scanner = new Scanner(file, "UTF-8");
			highScore = scanner.nextInt();
			scanner.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	private void saveScore(int score) {
		File file = new File("data/highscore.txt");
		 try {
			 BufferedWriter bufwriter = new BufferedWriter(new FileWriter(file));
			 bufwriter.write(Integer.toString(score));//writes the edited string buffer to the new file
			 bufwriter.close();//closes the file
		 } catch (Exception e) {//if an exception occurs
			             System.out.println("Error occured while attempting to write to file: " + e.getMessage());
        }

	}

	@Override
	public void stop() {
		// TODO Auto-generated method stub
		super.stop();
	}
	
	

}
